package com.swak.registry.memory;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import com.swak.Constants;
import com.swak.codec.Encodes;
import com.swak.registry.AbstractRegistry;
import com.swak.registry.NotifyListener;
import com.swak.registry.URL;
import com.swak.utils.CollectionUtils;
import com.swak.utils.Lists;
import com.swak.utils.Sets;
import com.swak.utils.StringUtils;

/**
 * MemoryRegistry.
 * 
 * @author 618lf
 */
public class MemoryRegistry extends AbstractRegistry {

	@FunctionalInterface
	interface Notify {
		void accept(String path);
	}

	private final ConcurrentMap<String, Set<URL>> urlsRegistered = new ConcurrentHashMap<>();
	private final ConcurrentMap<String, Map<NotifyListener, Notify>> notifiers = new ConcurrentHashMap<>();

	public MemoryRegistry(URL url) {
		super(url);
	}

	@Override
	public void register(URL url) {
		super.register(url);
		for (String path : url.toCategoryPaths()) {
			Set<URL> urls = urlsRegistered.computeIfAbsent(path, (key) -> {
				return Sets.newConcurrentSet();
			});
			urls.add(url);
			doNotify(path);
		}
	}

	@Override
	public void unregister(URL url) {
		super.unregister(url);
		for (String path : url.toCategoryPaths()) {
			Set<URL> urls = urlsRegistered.computeIfAbsent(path, (key) -> {
				return Sets.newConcurrentSet();
			});
			urls.remove(url);
			doNotify(path);
		}
	}

	private void doNotify(String path) {
		String key = path;
		Map<NotifyListener, Notify> notifys = null;
		while (key.contains(Constants.PATH_SEPARATE)) {
			if (notifiers.containsKey(key)) {
				notifys = notifiers.get(key);
				break;
			}
			key = StringUtils.substringBeforeLast(key, Constants.PATH_SEPARATE);
			if (StringUtils.isBlank(key)) {
				break;
			}
		}
		if (notifys == null || notifys.size() == 0) {
			return;
		}
		for (Notify notify : notifys.values()) {
			notify.accept(key);
		}
	}

	@Override
	public void subscribe(URL url, NotifyListener listener) {
		super.subscribe(url, listener);
		this.doSubscribe(url, listener);
	}

	private void doSubscribe(URL url, NotifyListener listener) {
		if (Constants.Subscribe_Any_Value.equals(url.getModule())) {
			this.doAnyModuleSubscribe(url, listener);
		} else if (Constants.Subscribe_Any_Value.equals(url.getService())) {
			this.doAnyServiceSubscribe(url, listener);
		} else {
			this.doCategorySubscribe(url, listener);
		}
	}

	private void doAnyModuleSubscribe(URL url, NotifyListener listener) {
		String path = url.toRootPath();
		Map<NotifyListener, Notify> listeners = this.notifiers.computeIfAbsent(path, k -> new ConcurrentHashMap<>());
		listeners.computeIfAbsent(listener, k -> (rootPath) -> {
			this.doAnyModuleSubscribe(rootPath, url, listener);
		});
		this.doAnyModuleSubscribe(path, url, listener);
	}

	private void doAnyModuleSubscribe(String path, URL url, NotifyListener listener) {
		List<String> keys = scanKeys(path);
		if (CollectionUtils.isNotEmpty(keys)) {
			Set<String> modules = Sets.newHashSet();
			for (String key : keys) {
				modules.add(this.toModule(key));
			}
			for (String module : modules) {
				subscribe(URL.valueOf(url).addParameter(Constants.Module_Key, module), listener);
			}
		}
	}

	private void doAnyServiceSubscribe(URL url, NotifyListener listener) {
		String path = url.toModulePath();
		Map<NotifyListener, Notify> listeners = this.notifiers.computeIfAbsent(path, k -> new ConcurrentHashMap<>());
		listeners.computeIfAbsent(listener, k -> (rootPath) -> {
			this.doAnyServiceSubscribe(rootPath, url, listener);
		});
		this.doAnyServiceSubscribe(path, url, listener);
	}

	private void doAnyServiceSubscribe(String path, URL url, NotifyListener listener) {
		List<String> keys = scanKeys(path);
		if (CollectionUtils.isNotEmpty(keys)) {
			Set<String> services = Sets.newHashSet();
			for (String key : keys) {
				services.add(this.toService(key));
			}
			for (String service : services) {
				subscribe(URL.valueOf(url).addParameter(Constants.Service_Key, service), listener);
			}
		}
	}

	private String toModule(String key) {
		String[] fragments = StringUtils.split(key, Constants.PATH_SEPARATE);
		return fragments.length > 1 ? fragments[1] : StringUtils.EMPTY;
	}

	private String toService(String key) {
		String[] fragments = StringUtils.split(key, Constants.PATH_SEPARATE);
		return fragments.length > 2 ? fragments[2] : StringUtils.EMPTY;
	}

	private List<String> scanKeys(String path) {
		return this.urlsRegistered.keySet().stream().filter(category -> category.startsWith(path)).toList();
	}

	private void doCategorySubscribe(URL url, NotifyListener listener) {
		for (String path : url.toCategoryPaths()) {
			Map<NotifyListener, Notify> listeners = this.notifiers.computeIfAbsent(path,
					k -> new ConcurrentHashMap<>());
			listeners.computeIfAbsent(listener, k -> (rootPath) -> {
				this.doCategorySubscribe(rootPath, url, listener);
			});
			this.doCategorySubscribe(path, url, listener);
		}
	}

	private void doCategorySubscribe(String path, URL url, NotifyListener listener) {
		List<String> urls = getChildrenPaths(path);
		MemoryRegistry.this.notify(url, listener, toUrls(url, urls));
	}

	private List<String> getChildrenPaths(String path) {
		Set<URL> registereds = this.urlsRegistered.get(path);
		if (registereds == null || registereds.size() == 0) {
			return null;
		}
		List<String> providers = Lists.newArrayList();
		for (URL url : registereds) {
			providers.add(url.toFullString());
		}
		return providers;
	}

	private List<URL> toUrls(URL consumer, List<String> providers) {
		List<URL> urls = new ArrayList<>();
		if (CollectionUtils.isNotEmpty(providers)) {
			for (String provider : providers) {
				URL url = URL.valueOf(Encodes.urlDecode(provider));
				if (consumer.match(url)) {
					urls.add(url);
				}
			}
		}
		return urls;
	}

	@Override
	public void unsubscribe(URL url, NotifyListener listener) {
		super.unsubscribe(url, listener);
	}
}